﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;

namespace XmlDSigEx
{
    public class ExcXmlC14NCanonicalisation:XmlC14NCanonicalisation
    {
        internal const string URI_EXC_C14N = "http://www.w3.org/2001/10/xml-exc-c14n#";

        protected override void MergeAncestorXmlAttributes(System.Xml.XmlElement ownerElement, List<System.Xml.XmlAttribute> attributes)
        {
            // No implementation.
        }

        protected override void ProcessNamespaceAxis(System.Xml.XmlElement xmlElement, List<System.Xml.XmlNode> nodeSet, System.IO.StreamWriter outputWriter)
        {
            XmlNodeList namespaceAxis = xmlElement.SelectNodes("namespace::*");
            List<XmlAttribute> namespaces = namespaceAxis.ConvertToListOf<XmlAttribute>();
            namespaces.RemoveAll(a => !nodeSet.Contains(a));
            namespaces.Sort(new NamespaceOrderComparer());

            // This block is to enforce the rules under section 2.3 of the spec concerning namespace axis.            
            if (namespaces.Count > 0 && namespaces[0].Name != "xmlns")
            {
                XmlElement nearestAncestor = FindNearestIncludedAncestor(xmlElement, nodeSet);
                if (nearestAncestor != null)
                {
                    XmlNodeList ancestorNamespaceAxis = nearestAncestor.SelectNodes("namespace::*");
                    // BUG! According to the spec, We should be checking that the ancestors node attribute
                    // should also be in the node set. However, it may have already been deleted from the nodeset.
                    // Need to fix.

                    // Is this fixed?
                    if (ancestorNamespaceAxis
                        .ConvertToListOf<XmlAttribute>()
                        .Exists(a => a.Name == "xmlns" && nodeSet.Contains(a)))
                    {
                        outputWriter.Write(" xmlns=\"\"");
                    }
                }
            }

            namespaces.RemoveAll(a => !ElementVisuallyUtilises(xmlElement, a) || RenderedByOutputAncestor(xmlElement, a));


            foreach (XmlAttribute namespaceNode in namespaces)
            {
                ProcessNamespaceNode(namespaceNode, nodeSet, xmlElement, outputWriter);
            }


        }

        protected override bool ShouldWriteNamespaceNode(XmlAttribute attributeNode, List<XmlNode> nodeSet, XmlElement ownerElement)
        {
            return true;
        }

        private bool RenderedByOutputAncestor(XmlElement xmlElement, XmlAttribute a)
        {
            string localName;
            if(a.LocalName == "xmlns")
                localName = "";
            else
                localName = a.LocalName;
            return _ns_rendered.Contains(new KeyValuePair<string, string>(localName, a.Value));
        }

        private bool ElementVisuallyUtilises(XmlElement xmlElement, XmlAttribute a)
        {
            if (xmlElement.Prefix == a.LocalName || (xmlElement.Prefix == "" && a.LocalName == "xmlns"))
                return true;
            foreach (XmlAttribute attribute in xmlElement.Attributes)
            {
                if (!IsNamespaceAttribute(attribute) && attribute.Prefix == a.LocalName)
                    return true;
            }
            return false;
        }

        private bool IsNamespaceAttribute(XmlAttribute attribute)
        {
            if (attribute.Prefix == string.Empty && attribute.LocalName == "xmlns")
                return true;
            if (attribute.Prefix == "xmlns")
                return true;
            return false;
        }
    }
}
